 /*******************************************************************************
  * Copyright (c) 2000, 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Sebastian Davids <sdavids@gmx.de> - Fix for bug 19346 - Dialog font should be
  * activated and used by other components.
  *******************************************************************************/
 package org.eclipse.ui.internal.ide.misc;

 import java.text.Collator ;
 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collection ;
 import java.util.Collections ;
 import java.util.Comparator ;
 import java.util.HashMap ;
 import java.util.Hashtable ;
 import java.util.Iterator ;
 import java.util.LinkedList ;
 import java.util.Map ;

 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.CheckboxTableViewer;
 import org.eclipse.jface.viewers.ICheckStateListener;
 import org.eclipse.jface.viewers.IContentProvider;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.ListViewer;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.internal.ide.Category;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.internal.ide.registry.Capability;
 import org.eclipse.ui.internal.ide.registry.CapabilityRegistry;
 import org.eclipse.ui.model.WorkbenchContentProvider;
 import org.eclipse.ui.model.WorkbenchLabelProvider;

 /**
  * A group of controls used to view and modify the
  * set of capabilities on a project.
  */
 public class ProjectCapabilitySelectionGroup {
     private static final String EMPTY_DESCRIPTION = "\n\n\n"; //$NON-NLS-1$

     private CapabilityRegistry registry;

     private Category[] initialCategories;

     private Capability[] initialCapabilities;

     private Capability[] disabledCapabilities;

     private boolean modified = false;

     private Text descriptionText;

     private CheckboxTableViewer checkboxViewer;

     private ICheckStateListener checkStateListener;

     private ArrayList visibleCapabilities = new ArrayList ();

     private ArrayList checkedCapabilities = new ArrayList ();

     private Collection disabledCaps;

     // For a given capability as key, the value will be a list of
 // other capabilities that require the capability. Also,
 // it may include the capability key if it was selected by the
 // user before being required by other capabilities.
 private HashMap dependents = new HashMap ();

     // For a given membership set id as key, the value is
 // a checked capability
 private HashMap memberships = new HashMap ();

     // Sort categories
 private Comparator categoryComparator = new Comparator () {
         private Collator collator = Collator.getInstance();

         public int compare(Object ob1, Object ob2) {
             Category c1 = (Category) ob1;
             Category c2 = (Category) ob2;
             return collator.compare(c1.getLabel(), c2.getLabel());
         }
     };

     // Sort capabilities
 private Comparator capabilityComparator = new Comparator () {
         private Collator collator = Collator.getInstance();

         public int compare(Object ob1, Object ob2) {
             Capability c1 = (Capability) ob1;
             Capability c2 = (Capability) ob2;
             return collator.compare(c1.getName(), c2.getName());
         }
     };

     /**
      * Creates a new instance of the <code>ProjectCapabilitySelectionGroup</code>
      *
      * @param categories the initial collection of valid categories to select
      * @param capabilities the intial collection of valid capabilities to select
      * @param registry all available capabilities registered by plug-ins
      */
     public ProjectCapabilitySelectionGroup(Category[] categories,
             Capability[] capabilities, CapabilityRegistry registry) {
         this(categories, capabilities, null, registry);
     }

     /**
      * Creates a new instance of the <code>ProjectCapabilitySelectionGroup</code>
      *
      * @param categories the initial collection of valid categories to select
      * @param capabilities the intial collection of valid capabilities to select
      * @param disabledCapabilities the collection of capabilities to show as disabled
      * @param registry all available capabilities registered by plug-ins
      */
     public ProjectCapabilitySelectionGroup(Category[] categories,
             Capability[] capabilities, Capability[] disabledCapabilities,
             CapabilityRegistry registry) {
         super();
         this.initialCategories = categories;
         this.initialCapabilities = capabilities;
         this.disabledCapabilities = disabledCapabilities;
         this.registry = registry;
     }

     /**
      * Create the contents of this group. The basic layout is a checkbox
      * list with a text field at the bottom to display the capability
      * description.
      */
     public Control createContents(Composite parent) {
         Font font = parent.getFont();
         // Create the main composite for the other controls
 Composite composite = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.numColumns = 2;
         layout.makeColumnsEqualWidth = true;
         composite.setLayout(layout);
         composite.setLayoutData(new GridData(GridData.FILL_BOTH));

         // Composite for category label and list...
 Composite catComposite = new Composite(composite, SWT.NONE);
         catComposite.setLayout(new GridLayout());
         catComposite.setLayoutData(new GridData(GridData.FILL_BOTH));

         // Add a label to identify the list viewer of categories
 Label categoryLabel = new Label(catComposite, SWT.LEFT);
         categoryLabel.setText(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_categories);
         GridData data = new GridData();
         data.verticalAlignment = SWT.TOP;
         categoryLabel.setLayoutData(data);
         categoryLabel.setFont(font);

         // List viewer of all available categories
 ListViewer listViewer = new ListViewer(catComposite);
         listViewer.getList().setLayoutData(new GridData(GridData.FILL_BOTH));
         listViewer.getList().setFont(font);
         listViewer.setLabelProvider(new WorkbenchLabelProvider());
         listViewer.setContentProvider(getContentProvider());
         listViewer.setInput(getAvailableCategories());

         // Composite for capability label and table...
 Composite capComposite = new Composite(composite, SWT.NONE);
         capComposite.setLayout(new GridLayout());
         capComposite.setLayoutData(new GridData(GridData.FILL_BOTH));

         // Add a label to identify the checkbox tree viewer of capabilities
 Label capabilityLabel = new Label(capComposite, SWT.LEFT);
         capabilityLabel.setText(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_capabilities);
         data = new GridData();
         data.verticalAlignment = SWT.TOP;
         capabilityLabel.setLayoutData(data);
         capabilityLabel.setFont(font);

         // Checkbox tree viewer of capabilities in selected categories
 checkboxViewer = CheckboxTableViewer.newCheckList(capComposite,
                 SWT.SINGLE | SWT.TOP | SWT.BORDER);
         checkboxViewer.getTable().setLayoutData(
                 new GridData(GridData.FILL_BOTH));
         checkboxViewer.getTable().setFont(font);
         checkboxViewer.setLabelProvider(new CapabilityLabelProvider());
         checkboxViewer.setContentProvider(getContentProvider());
         checkboxViewer.setInput(visibleCapabilities);

         // Add a label to identify the text field of capability's description
 Label descLabel = new Label(composite, SWT.LEFT);
         descLabel.setText(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_description);
         data = new GridData();
         data.verticalAlignment = SWT.TOP;
         data.horizontalSpan = 2;
         descLabel.setLayoutData(data);
         descLabel.setFont(font);

         // Text field to display the capability's description
 descriptionText = new Text(composite, SWT.WRAP | SWT.MULTI
                 | SWT.V_SCROLL | SWT.BORDER);
         descriptionText.setText(EMPTY_DESCRIPTION);
         descriptionText.setEditable(false);
         data = new GridData();
         data.horizontalAlignment = GridData.FILL;
         data.grabExcessHorizontalSpace = true;
         data.horizontalSpan = 2;
         descriptionText.setLayoutData(data);
         descriptionText.setFont(font);

         // Add a text field to explain grayed out items
 Label grayLabel = new Label(composite, SWT.LEFT);
         grayLabel.setText(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_grayItems);
         data = new GridData();
         data.verticalAlignment = SWT.TOP;
         data.horizontalSpan = 2;
         grayLabel.setLayoutData(data);
         grayLabel.setFont(font);

         // Setup initial context
 populateDependents();
         populateMemberships();

         // Listen for selection changes to update the description field
 checkboxViewer
                 .addSelectionChangedListener(new ISelectionChangedListener() {
                     public void selectionChanged(SelectionChangedEvent event) {
                         updateDescription(event.getSelection());
                     }
                 });

         // Properly handle user checking and unchecking project features
 checkboxViewer.addCheckStateListener(new ICheckStateListener() {
             public void checkStateChanged(CheckStateChangedEvent event) {
                 Capability cap = (Capability) event.getElement();
                 if (event.getChecked())
                     handleCapabilityChecked(cap);
                 else
                     handleCapabilityUnchecked(cap);
                 checkboxViewer.setSelection(new StructuredSelection(cap));
             }
         });

         // Listen for category selection and update the list of capabilities
 listViewer.addSelectionChangedListener(new ISelectionChangedListener() {
             public void selectionChanged(SelectionChangedEvent event) {
                 if (event.getSelection() instanceof IStructuredSelection) {
                     IStructuredSelection sel = (IStructuredSelection) event
                             .getSelection();
                     visibleCapabilities.clear();
                     Iterator itr = sel.iterator();
                     while (itr.hasNext()) {
                         Category cat = (Category) itr.next();
                         visibleCapabilities.addAll(cat.getElements());
                     }
                     Collections.sort(visibleCapabilities, capabilityComparator);
                     checkboxViewer.refresh();
                     itr = visibleCapabilities.iterator();
                     while (itr.hasNext()) {
                         Capability cap = (Capability) itr.next();
                         if (hasDependency(cap))
                             checkboxViewer.setGrayed(cap, true);
                         if (checkedCapabilities.contains(cap))
                             checkboxViewer.setChecked(cap, true);
                     }
                     updateDescription(checkboxViewer.getSelection());
                 }
             }
         });

         // initialize
 if (initialCapabilities != null)
             checkedCapabilities.addAll(Arrays.asList(initialCapabilities));
         if (initialCategories != null)
             listViewer.setSelection(new StructuredSelection(initialCategories));

         return composite;
     }

     /**
      * Marks the capability as being checked.
      */
     private void markCapabilityChecked(Capability target, Capability dependent) {
         // Check the target capability
 if (!checkedCapabilities.contains(target))
             checkedCapabilities.add(target);
         checkboxViewer.setChecked(target, true);

         // Gray the target to show the user its required
 // by another capability.
 if (target != dependent)
             checkboxViewer.setGrayed(target, true);

         // Update the dependent map for the target capability
 addDependency(target, dependent);

         // Update the membership set for the target capability
 String [] ids = registry.getMembershipSetIds(target);
         for (int j = 0; j < ids.length; j++)
             memberships.put(ids[j], target);
     }

     /**
      * Marks the capability as being unchecked.
      */
     private void markCapabilityUnchecked(Capability target) {
         // Uncheck the target capability
 checkedCapabilities.remove(target);
         checkboxViewer.setChecked(target, false);

         // Ungray the target as there is no dependency on it
 checkboxViewer.setGrayed(target, false);

         // Remove the dependency entry
 dependents.remove(target);

         // Update the membership set for the target capability
 String [] ids = registry.getMembershipSetIds(target);
         for (int j = 0; j < ids.length; j++) {
             if (memberships.get(ids[j]) == target)
                 memberships.remove(ids[j]);
         }
     }

     /**
      * Returns the list of categories that have capabilities
      * registered against it.
      */
     private ArrayList getAvailableCategories() {
         ArrayList results = registry.getUsedCategories();
         Collections.sort(results, categoryComparator);
         if (registry.getMiscCategory() != null)
             results.add(registry.getMiscCategory());
         return results;
     }

     /**
      * Return <code>true</code> if the user may have made changes
      * to the capabilities of the project. Otherwise <code>false</code>
      * if no changes were made.
      *
      * @return <code>true</true> when possible changes may have been made,
      * <code>false</code> otherwise
      */
     public boolean getCapabilitiesModified() {
         return modified;
     }

     /**
      * Returns the content provider for the viewers
      */
     private IContentProvider getContentProvider() {
         return new WorkbenchContentProvider() {
             public Object [] getChildren(Object parentElement) {
                 if (parentElement instanceof ArrayList )
                     return ((ArrayList ) parentElement).toArray();
                 else
                     return null;
             }
         };
     }

     /**
      * The user has changed the project capability selection.
      * Set the modified flag and clear the caches.
      */
     private void capabilitiesModified() {
         modified = true;
     }

     /**
      * Add a dependency between the target and dependent
      * capabilities
      */
     private void addDependency(Capability target, Capability dependent) {
         ArrayList descriptors = (ArrayList ) dependents.get(target);
         if (descriptors == null) {
             descriptors = new ArrayList ();
             descriptors.add(dependent);
             dependents.put(target, descriptors);
         } else if (!descriptors.contains(dependent)) {
             descriptors.add(dependent);
         }
     }

     /**
      * Returns true if the capability has any
      * dependencies on it.
      */
     private boolean hasDependency(Capability capability) {
         ArrayList descriptors = (ArrayList ) dependents.get(capability);
         if (descriptors == null)
             return false;
         if (descriptors.size() == 1 && descriptors.get(0) == capability)
             return false;
         return true;
     }

     /**
      * Returns whether the category is considered disabled
      */
     private boolean isDisabledCapability(Capability cap) {
         if (disabledCaps == null) {
             if (disabledCapabilities == null)
                 disabledCaps = new ArrayList (0);
             else
                 disabledCaps = Arrays.asList(disabledCapabilities);
         }
         return disabledCaps.contains(cap);
     }

     /**
      * Populate the dependents map based on the
      * current set of capabilities.
      */
     private void populateDependents() {
         if (initialCapabilities == null)
             return;

         LinkedList capabilities = new LinkedList ();
         capabilities.addAll(Arrays.asList(initialCapabilities));

         while (!capabilities.isEmpty()) {
             // Retrieve the target capability
 Capability target;
             target = (Capability) capabilities.removeFirst();
             // Add the capability as a dependent of itself.
 // It will indicate to the uncheck handler to not uncheck this
 // capability automatically even if a another capability which
 // depended on it is unchecked.
 addDependency(target, target);

             if (registry.hasPrerequisites(target)) {
                 // Retrieve the prerequisite capabilities...
 String [] prereqIds = registry.getPrerequisiteIds(target);
                 Capability[] prereqCapabilities;
                 prereqCapabilities = registry.findCapabilities(prereqIds);
                 // For each prerequisite capability...
 for (int i = 0; i < prereqCapabilities.length; i++) {
                     // Update the dependent map for the prerequisite capability
 addDependency(prereqCapabilities[i], target);
                     // Recursive if prerequisite capability also has prerequisites
 if (registry.hasPrerequisites(prereqCapabilities[i]))
                         capabilities.addLast(prereqCapabilities[i]);
                 }
             }
         }
     }

     /**
      * Populate the memberships map based on the
      * current set of capabilities.
      */
     private void populateMemberships() {
         if (initialCapabilities == null)
             return;

         Iterator itr = (Arrays.asList(initialCapabilities)).iterator();
         while (itr.hasNext()) {
             Capability cap = (Capability) itr.next();
             String [] ids = registry.getMembershipSetIds(cap);
             for (int j = 0; j < ids.length; j++) {
                 memberships.put(ids[j], cap);
             }
         }
     }

     /**
      * Handle the case of a capability being checked
      * by ensuring the action is allowed and the prerequisite
      * capabilities are also checked.
      */
     private void handleCapabilityChecked(Capability capability) {
         // Cannot allow a disabled capability to be checked
 if (isDisabledCapability(capability)) {
             MessageDialog
                     .openWarning(
                             checkboxViewer.getControl().getShell(),
                             IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                             NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_disabledCapability, capability.getName()));
             checkboxViewer.setChecked(capability, false);
             return;
         }

         // Cannot allow an invalid capability to be checked
 if (!capability.isValid()) {
             MessageDialog
                     .openWarning(
                             checkboxViewer.getControl().getShell(),
                             IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                             NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_invalidCapability, capability.getName()));
             checkboxViewer.setChecked(capability, false);
             return;
         }

         // Is there a membership set problem...
 String [] ids = registry.getMembershipSetIds(capability);
         for (int i = 0; i < ids.length; i++) {
             Capability member = (Capability) memberships.get(ids[i]);
             if (member != null && member != capability) {
                 MessageDialog
                         .openWarning(
                                 checkboxViewer.getControl().getShell(),
                                 IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                                 NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_membershipConflict, capability.getName(), member.getName()));
                 checkboxViewer.setChecked(capability, false);
                 return;
             }
         }

         // Handle prerequisite by auto-checking them if possible
 if (registry.hasPrerequisites(capability)) {
             // Check for any prerequisite problems...
 // Retrieve all the prerequisite capabilities, including
 // any prerequisite of the prerequisites!
 LinkedList capabilities = new LinkedList ();
             capabilities.addLast(capability);
             while (!capabilities.isEmpty()) {
                 Capability target;
                 target = (Capability) capabilities.removeFirst();
                 // Retrieve the capability's immediate prerequisites
 String [] prereqIds = registry.getPrerequisiteIds(target);
                 Capability[] prereqCapabilities;
                 prereqCapabilities = registry.findCapabilities(prereqIds);
                 for (int i = 0; i < prereqCapabilities.length; i++) {
                     // If the prerequisite is missing, warn the user and
 // do not allow the check to proceed.
 if (prereqCapabilities[i] == null
                             || isDisabledCapability(prereqCapabilities[i])
                             || !prereqCapabilities[i].isValid()) {
                         MessageDialog
                                 .openWarning(
                                         checkboxViewer.getControl().getShell(),
                                         IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                                         NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_missingPrereqs, capability.getName(), prereqIds[i]));
                         checkboxViewer.setChecked(capability, false);
                         return;
                     }
                     // If there is a membership problem, warn the user and
 // do not allow the check to proceed
 ids = registry.getMembershipSetIds(prereqCapabilities[i]);
                     for (int j = 0; j < ids.length; j++) {
                         Capability member = (Capability) memberships
                                 .get(ids[j]);
                         if (member != null && member != prereqCapabilities[i]) {
                             MessageDialog
                                     .openWarning(
                                             checkboxViewer.getControl()
                                                     .getShell(),
                                             IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                                             NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_membershipPrereqConflict, new Object [] {capability.getName(), prereqCapabilities[i].getName(), member.getName()}));
                             checkboxViewer.setChecked(capability, false);
                             return;
                         }
                     }
                     // If the prerequisite capability has prerequisites
 // also, then add it to be processed.
 if (registry.hasPrerequisites(prereqCapabilities[i]))
                         capabilities.addLast(prereqCapabilities[i]);
                 }
             }

             // Auto-check all prerequisite capabilities
 capabilities = new LinkedList ();
             capabilities.addLast(capability);
             // For each capability that has prerequisites...
 while (!capabilities.isEmpty()) {
                 Capability target;
                 target = (Capability) capabilities.removeFirst();
                 // Retrieve the prerequisite capabilities...
 String [] prereqIds = registry.getPrerequisiteIds(target);
                 Capability[] prereqCapabilities;
                 prereqCapabilities = registry.findCapabilities(prereqIds);
                 // For each prerequisite capability...
 for (int i = 0; i < prereqCapabilities.length; i++) {
                     // Mark it as being checked
 markCapabilityChecked(prereqCapabilities[i], target);
                     // Recursive if prerequisite capability also has prerequisites
 if (registry.hasPrerequisites(prereqCapabilities[i]))
                         capabilities.addLast(prereqCapabilities[i]);
                 }
             }
         }

         // Mark the capability as checked. Adds itself as a
 // dependent - this will indicate to the uncheck handler
 // to not uncheck this capability automatically even if
 // another capability which depends on it is unchecked.
 markCapabilityChecked(capability, capability);

         // Notify those interested
 capabilitiesModified();
         notifyCheckStateListner();
     }

     /**
      * Handle the case of a capability being unchecked
      * by ensuring the action is allowed.
      */
     private void handleCapabilityUnchecked(Capability capability) {
         ArrayList descriptors = (ArrayList ) dependents.get(capability);

         // Note, there is no need to handle the case where descriptors size
 // is zero because it cannot happen. For this method to be called, the
 // item must have been checked previously. If it was checked by the user,
 // then the item itself would be a dependent. If the item was checked
 // because it was required by another capability, then that other capability
 // would be a dependent.

         if (descriptors.size() == 1 && descriptors.get(0) == capability) {
             // If the only dependent is itself, then its ok to uncheck
 capabilitiesModified();
             markCapabilityUnchecked(capability);

             // Remove this capability as a dependent on its prerequisite
 // capabilities. Recursive if a prerequisite capability
 // no longer has any dependents.
 if (registry.hasPrerequisites(capability)) {
                 LinkedList capabilities = new LinkedList ();
                 capabilities.addLast(capability);
                 // For each capability that has prerequisite capabilities
 while (!capabilities.isEmpty()) {
                     Capability target;
                     target = (Capability) capabilities.removeFirst();
                     // Retrieve the prerequisite capabilities...
 String [] prereqIds = registry.getPrerequisiteIds(target);
                     Capability[] prereqCapabilities;
                     prereqCapabilities = registry.findCapabilities(prereqIds);
                     // For each prerequisite capability...
 for (int i = 0; i < prereqCapabilities.length; i++) {
                         // Retrieve the list of dependents on the prerequisite capability...
 Capability prereqCap = prereqCapabilities[i];
                         ArrayList prereqDependents = (ArrayList ) dependents
                                 .get(prereqCap);
                         // Remove the dependent target capability...
 prereqDependents.remove(target);
                         if (prereqDependents.isEmpty()) {
                             // Unchecked the prerequisite capability
 markCapabilityUnchecked(prereqCap);
                             // Recursive if prerequisite capability also has
 // prerequisite capabilities
 if (registry.hasPrerequisites(prereqCap))
                                 capabilities.addLast(prereqCap);
                         } else if (prereqDependents.size() == 1
                                 && prereqDependents.get(0) == prereqCap) {
                             // Only dependent is itself so ungray the item to let the
 // user know no other capability is dependent on it
 checkboxViewer.setGrayed(prereqCap, false);
                         }
                     }
                 }
             }

             // Notify those interested
 notifyCheckStateListner();
         } else {
             // At least one other capability depends on it being checked
 // so force it to remain checked and warn the user.
 checkboxViewer.setChecked(capability, true);
             // Get a copy and remove the target capability
 ArrayList descCopy = (ArrayList ) descriptors.clone();
             descCopy.remove(capability);
             // Show the prereq problem to the user
 if (descCopy.size() == 1) {
                 Capability cap = (Capability) descCopy.get(0);
                 MessageDialog
                         .openWarning(
                                 checkboxViewer.getControl().getShell(),
                                 IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                                 NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_requiredPrereq, capability.getName(), cap.getName()));
             } else {
                 StringBuffer msg = new StringBuffer ();
                 Iterator itr = descCopy.iterator();
                 while (itr.hasNext()) {
                     Capability cap = (Capability) itr.next();
                     msg.append("\n "); //$NON-NLS-1$
 msg.append(cap.getName());
                 }
                 MessageDialog
                         .openWarning(
                                 checkboxViewer.getControl().getShell(),
                                 IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_errorTitle,
                                 NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_requiredPrereqs, capability.getName(), msg));
             }
         }
     }

     /**
      * Returns the collection of capabilities selected
      * by the user. The collection is not in prerequisite
      * order.
      *
      * @return array of selected capabilities
      */
     public Capability[] getSelectedCapabilities() {
         Capability[] capabilities = new Capability[checkedCapabilities.size()];
         checkedCapabilities.toArray(capabilities);
         return capabilities;
     }

     /**
      * Return the current listener interested when the check
      * state of a capability actually changes.
      *
      * @return Returns a ICheckStateListener
      */
     public ICheckStateListener getCheckStateListener() {
         return checkStateListener;
     }

     /**
      * Set the current listener interested when the check
      * state of a capability actually changes.
      *
      * @param checkStateListener The checkStateListener to set
      */
     public void setCheckStateListener(ICheckStateListener checkStateListener) {
         this.checkStateListener = checkStateListener;
     }

     /**
      * Notify the check state listener that a capability
      * check state has changed. The event past will
      * always be <code>null</code> as it could be
      * triggered by code instead of user input.
      */
     private void notifyCheckStateListner() {
         if (checkStateListener != null)
             checkStateListener.checkStateChanged(null);
     }

     /**
      * Updates the description field for the selected capability
      */
     private void updateDescription(ISelection selection) {
         String text = EMPTY_DESCRIPTION;
         if (selection instanceof IStructuredSelection) {
             IStructuredSelection sel = (IStructuredSelection) selection;
             Capability cap = (Capability) sel.getFirstElement();
             if (cap != null)
                 text = cap.getDescription();
         }
         descriptionText.setText(text);
     }

     class CapabilityLabelProvider extends LabelProvider {
         private Map imageTable;

         public void dispose() {
             if (imageTable != null) {
                 Iterator itr = imageTable.values().iterator();
                 while (itr.hasNext())
                     ((Image) itr.next()).dispose();
                 imageTable = null;
             }
         }

         public Image getImage(Object element) {
             ImageDescriptor descriptor = ((Capability) element)
                     .getIconDescriptor();
             if (descriptor == null)
                 return null;

             //obtain the cached image corresponding to the descriptor
 if (imageTable == null) {
                 imageTable = new Hashtable (40);
             }
             Image image = (Image) imageTable.get(descriptor);
             if (image == null) {
                 image = descriptor.createImage();
                 imageTable.put(descriptor, image);
             }
             return image;
         }

         public String getText(Object element) {
             Capability cap = (Capability) element;
             String text = cap.getName();
             if (isDisabledCapability(cap))
                 text = NLS.bind(IDEWorkbenchMessages.ProjectCapabilitySelectionGroup_disabledLabel, text );
             return text;
         }
     }
 }


